import {
    getCurrentInstance,
    inject,
    reactive,
    computed,
    toRefs,
    isRef,
    isReactive,
    watch,
    effectScope,
} from "vue";
import { activePinia, piniaSymbol, setActivePinia } from "./rootStore";
import { addSubscription, triggerSubscriptions } from "./subscribe";
import { isComputed, mergeReactiveObject } from "./utils";

function createOptionsStore(id, options, pinia) {
    // 解构获取 store 中状态 state, 计算属性 getters  方法 actions
    const { state, getters, actions } = options;
    // 处理用户传入的 state, getters, actions 整合构建为 setup 函数
    function setup() {
        // 将当前 store 中 state 存放到 pinia.state 全局中(没传入)为假
        pinia.state.value[id] = state ? state() : {}; // 最终得到普通对象(无响应式)

        // 将 store 中状态转化为响应式 (普通值没法响应式需要转化为 ref 数据) 不然使用 $patch 不触发页面更新
        const localState = toRefs(pinia.state.value[id]);

        // 将用户 store 数据合并为一个大对象
        return Object.assign(
            localState, // 用户状态
            Object.keys(getters || {}).reduce((memo, name) => {
                // 用户计算属性
                memo[name] = computed(() => {
                    // 获取当前 store
                    const store = pinia._s.get(id);
                    return getters[name].call(store); // 使 this 指向始终为当前 store
                });
                return memo; // 用户的 getters 中方法都转化为计算属性
            }, {}),
            actions // 用户方法
        );
    }
    const store = createSetupStore(id, setup, pinia, true); // true 代表是 option api (state 我们已经处理自动添加到 pinia.state 中)
    // $reset 内置方法只支持 (option api)
    store.$reset = function () {
        const newState = state ? state() : {}; // 获取初始化的状态
        // 调用自己更新状态的方法
        store.$patch((state) => {
            Object.assign(state, newState); // 默认状态进行合并覆盖
        });
    };
}

// 核心方法
function createSetupStore(id, setup, pinia, isOption) {
    // scope 主要作用是可以自己停止自己
    let scope;
    // 内置 $patch 更新状态
    function $patch(partialStateOrMutatior) {
        // 用户传入更新状态可能为对象也可能为函数
        if (typeof partialStateOrMutatior === "object") {
            // 要将传入的对象于全局中 pinia.state 对应 store 状态合并 (注意 pinia.state.value[id] 一定要为响应式不然普通对象改变不触发视图更新)
            mergeReactiveObject(pinia.state.value[id], partialStateOrMutatior);
        } else {
            partialStateOrMutatior(pinia.state.value[id]); // 直接在全局找到对应的 store 中状态就行更新
        }
    }
    let actionSubscriptions = []; // 存放用户订阅监听方法的队列
    // 内置的部分 store 方法
    const partialStore = {
        $patch,
        // 监听状态发送变化
        $subscribe(callback, options = {}) {
            // 在作用域中监听值变化
            scope.run(() =>
                watch(
                    pinia.state.value[id],
                    (state) => {
                        // 回调参数一为一些信息(这里返回简单和官方并不一致官方还有 type 为改变类型), 参数二为新状态
                        callback({ storeId: id }, state);
                    },
                    options // 监听的其他配置参数
                )
            );
        },
        // 监听用户调用方法
        $onAction: addSubscription.bind(null, actionSubscriptions),
        // 停止响应式数据
        $dispose() {
            scope.stop(); // 清除响应式
            actionSubscriptions = []; // 清空订阅
            pinia._s.delete(id); // 全局删除 store
        },
    };
    // 存放一些内置 api 在 store 上如 $pacth, $subscribe 方法
    const store = reactive(partialStore); // 存放 store 中响应式对象数据

    const initialState = pinia.state.value[id]; // 获取当前 store 存放在全局 pinia.state 中值(setup api 是没有存放过返回 undefined)
    // 全局中没有且不为 option api
    if (!initialState && !isOption) {
        pinia.state.value[id] = {}; // setup API 现在全局 pinia.state 设置默认空对象
    }

    // 父亲可以停止所有, setupStore 是用户传递的属性和方法
    const setupStore = pinia._e.run(() => {
        scope = effectScope(); // 自己停止自己
        return scope.run(() => setup());
    });
    // console.log(setupStore); //  实际返回就算 setup 函数返回值
    // pinia._s.stop() 停止全部 store
    // scope.stop() 停止自己 store
    // 处理 action 绑定的 this 问题

    // 处理 action 方法 this 问题以及其他
    function wrapAction(action) {
        return function () {
            // 方法执行时触发事件
            const afterCallBackList = [];
            const onErrorCallBackList = [];
            function after(callback) {
                afterCallBackList.push(callback);
            }
            function onError(callback) {
                onErrorCallBackList.push(callback);
            }
            // 发布事件传入参数对象 { after, onError }
            triggerSubscriptions(actionSubscriptions, { after, onError });
            let ret;
            try {
                ret = action.apply(store, arguments); // 绑定 this 不受改变
            } catch (e) {
                triggerSubscriptions(onErrorCallBackList, e); // 发布错误队列的订阅
            }
            // todo 执行后可能为 promise
            if (ret instanceof Promise) {
                // action 可能是 promise
                return ret
                    .then((value) => {
                        triggerSubscriptions(afterCallBackList, value);
                        return value;
                    })
                    .catch((e) => {
                        triggerSubscriptions(onErrorCallBackList, e);
                        return Promise.reject(e);
                    });
            }
            // 普通方法
            triggerSubscriptions(afterCallBackList, ret);
            return ret;
        };
    }

    // 遍历用户传入所有数据
    for (let key in setupStore) {
        const prop = setupStore[key]; // action 中方法
        if (typeof prop === "function") {
            // 只处理函数(action 中全是方法动作)
            setupStore[key] = wrapAction(prop);
        }
        // 这里进行判断 setup api 中哪些是状态，是状态则存放到 pinia.state 中去
        if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
            // setup api 中定义的状态一般是使用 ref 或者 reactive 定义但是不包括计算属性
            if (!isOption) {
                pinia.state.value[id][key] = prop; // 存入到全局 pinia.state 中去
            }
        }
    }

    store.$id = id; // store 中定义内置 $id
    pinia._s.set(id, store); // 将 store 和 id 进行映射对应存起来
    Object.assign(store, setupStore); // 合并内置和用户的两个 store

    // 操作 store 所有属性 (代理 $state 属性)
    Object.defineProperty(store, "$state", {
        get: () => pinia.state.value[id], // 获取当前 store 中 state 状态
        set: (state) => {
            // 使用内置 $patch 更新数据 (上面已经实现)
            $patch(($state) => Object.assign($state, state)); // 合并数据);
        },
    });

    // 遍历插件
    pinia._p.forEach((plugin) => {
        // store 与 插件返回数据合并
        Object.assign(
            store,
            // 给插件传入对象中包含 store 核心, (官方实际还有其他数据)
            scope.run(() => plugin({ store })) // 插件返回的数据
        );
    });

    return store;
}

export function defineStore(idOrOptions, setup) {
    let id;
    let options;
    if (typeof idOrOptions === "string") {
        id = idOrOptions;
        options = setup;
    } else {
        options = idOrOptions;
        id = idOrOptions.id;
    }

    // pinia 写法有两种 (一种 options API 传入是对象，另一种 setup API 传入是函数)
    const isSetupStore = typeof setup === "function";

    function useStore() {
        const instance = getCurrentInstance(); // 获取当前的组件实例
        let pinia = instance && inject(piniaSymbol); // 获取 store 拿取同一个

        // 判断是否有 pinia (因为在路由中没有组件可能获取不到)
        if (pinia) {
            setActivePinia(pinia);
        }

        pinia = activePinia; // 获取全局的 pinia

        // 判断是否已经存入过(没有存放先进行存入处理)  第一次 useStore
        if (!pinia._s.has(id)) {
            // 是否 composition 的 setup 方法
            if (isSetupStore) {
                createSetupStore(id, setup, pinia); // composition api
            } else {
                createOptionsStore(id, options, pinia); // option api
            }
        }

        // 后续通过 id 获取 store 返回当前 store
        const store = pinia._s.get(id);

        // todo 此处可以做一些其他处理

        return store;
    }

    useStore.$id = id; // 用于 mapStores 取值

    return useStore;
}
